/*************************************************************
  Cocus Core - A simple PIC16 core emulator for PIC18
  
  version 2.0 by Santiago H. (santiagohssl@gmail.com)

  UNDER GNU LGPL

  file: vmcore.c
*************************************************************/

#include <p18cxxx.h>
#include "vmcore.h"
#include "vmasm.h"

#ifdef PERIPH_USART
#include "service_usart.h"
#endif

void vm_init(void *dev)
{
	core2_header * p = dev;
	
	// clear W, and stack on Dev
	p->w = 0;
	p->current_stack = 0;
	p->stack[0] = 0;

	/* jump to reset vector */
	vm_setPC(dev, CORE_VECTOR_RESET);

	// set STATUS (0x003) = 0x18
	vm_writeRam(dev, COREDEV_SFR_STATUS, 0x18);
}


/* *******************************************************************************
	RAM I/O
 ******************************************************************************* */
char vm_readRam(void *dev, int pos)
{
	/* write vm ram */
	return *((char*)dev+COREDEV_RAMSTART+pos);
}

void vm_writeRam(void *dev, int pos, char data)
{
	/* write vm ram */
	*((char*)dev+COREDEV_RAMSTART+pos) = data;
}


/* banks */

char vm_getBank(void *dev)
{
	vm_bytebits		status;
	
	status.ch = vm_readRam(dev, COREDEV_SFR_STATUS);
	
	return (status.bits.b5) || (status.bits.b6 << 1);
}

unsigned int vm_bankMemDiff(void *dev)
{
	return (unsigned int)vm_getBank(dev) << 7;
}

unsigned int vm_getSFRaddress(void *dev, int pos)
{
	/* repeated SFR across banks */
	switch (pos)
	{
		case COREDEV_SFR_INDF:
			/* when accessing indirectly, first read the register pointed by FSR and then
			   add 0x100 if IRP bit is enabled */
			pos = vm_readRam(dev, COREDEV_SFR_FSR);
			if (vm_readSingleBit(dev, COREDEV_SFR_STATUS, COREDEV_BIT_IRP))
				pos += 0x100;
				
			//return pos;
		
		case COREDEV_SFR_STATUS:
		case COREDEV_SFR_PCL:
		case COREDEV_SFR_PCLATH:
		case COREDEV_SFR_FSR:
			return pos;
			
		case COREDEV_SFR_INTCON:
			if (vm_getBank(dev) == 1)
				return pos + vm_bankMemDiff(dev);
			else
				return pos;

		default:
			return pos + vm_bankMemDiff(dev);
	}
}


/* exposed functions to guest */
char vm_readRamExposed(void *dev, int pos)
{
	char data;

	pos = vm_getSFRaddress(dev, pos);
	
	data = vm_readRam(dev, pos);
	
	#ifdef PERIPH_USART
		if (vm_services_usart_read_hook(dev, pos, &data) == CORE_HOOK_DISCARD_OP) // usart hook
			return data;
	#endif	
	
	return data;
}

void vm_writeRamExposed(void *dev, int pos, char data)
{
	pos = vm_getSFRaddress(dev, pos);

	#ifdef PERIPH_USART
		if (vm_services_usart_write_hook(dev, pos, &data) == CORE_HOOK_DISCARD_OP)
			return;
	#endif
	
	vm_writeRam(dev, pos, data);
}

void vm_writeRamExposedWithW(void *dev, int pos, char data, char target)
{
	if (target)
		// d = 1, target = register f
		vm_writeRamExposed(dev, pos, data);
	else
		// d = 0, target = w
		vm_setW(dev, data);		
}



/* bits */

char vm_readSingleBit(void *dev, int pos, int bitn)
{
	char membyte = vm_readRam(dev, pos);
	return (membyte >> bitn) & 0x1;
}

void vm_writeSingleBit(void *dev, int pos, int bitn, char data)
{
	vm_bytebits membyte;
	membyte.ch = vm_readRam(dev, pos);
	
	switch (bitn)
	{
		case 0: membyte.bits.b0 = data; 	break;
		case 1: membyte.bits.b1 = data; 	break;
		case 2: membyte.bits.b2 = data; 	break;
		case 3: membyte.bits.b3 = data; 	break;
		case 4: membyte.bits.b4 = data; 	break;
		case 5: membyte.bits.b5 = data; 	break;
		case 6: membyte.bits.b6 = data; 	break;
		case 7: membyte.bits.b7 = data; 	break;
	}

	vm_writeRam(dev, pos, membyte.ch);
}

/* exposed functions to guest */
char vm_readSingleBitExposed(void *dev, int pos, int bitn)
{
	char membyte = vm_readRamExposed(dev, pos);
	return (membyte >> bitn) & 0x1;
}

void vm_writeSingleBitExposed(void *dev, int pos, int bitn, char data)
{
	vm_bytebits membyte;
	membyte.ch = vm_readRamExposed(dev, pos);
	
	switch (bitn)
	{
		case 0: membyte.bits.b0 = data; 	break;
		case 1: membyte.bits.b1 = data; 	break;
		case 2: membyte.bits.b2 = data; 	break;
		case 3: membyte.bits.b3 = data; 	break;
		case 4: membyte.bits.b4 = data; 	break;
		case 5: membyte.bits.b5 = data; 	break;
		case 6: membyte.bits.b6 = data; 	break;
		case 7: membyte.bits.b7 = data; 	break;
	}

	vm_writeRamExposed(dev, pos, membyte.ch);	
}

void vm_setZifZero(void *dev, char data)
{
	if (data)
		vm_status_set_z(dev, 0);
	else
		vm_status_set_z(dev, 1);
}

char vm_getW(void *dev)
{
	core2_header * p = dev;
	return p->w;
}

void vm_setW(void *dev, char w)
{
	core2_header * p = dev;
	p->w = w;
}

/* *******************************************************************************
	PC & Stack
 ******************************************************************************* */
 
unsigned int vm_getPC(void *dev)
{
 	vm_intbytes pc;
 	
 	pc.bytes.low = vm_readRam(dev, COREDEV_SFR_PCL);
 	pc.bytes.high = vm_readRam(dev, COREDEV_SFR_PCLATH);
 	
 	return pc.data;
}
 
void vm_setPC(void *dev, int pc)
{
 	vm_intbytes bpc;
 	bpc.data = pc;
 	
 	vm_writeRam(dev, COREDEV_SFR_PCL, bpc.bytes.low);
 	vm_writeRam(dev, COREDEV_SFR_PCLATH, bpc.bytes.high);
}
 
void vm_sumPC(void *dev)
{
 	int pc = vm_getPC(dev);
	vm_setPC(dev, ++pc);
}
 
unsigned char vm_getSelectedStack(void *dev)
{
	core2_header * p = dev;
 	return p->current_stack;
}

void vm_setSelectedStack(void *dev, char sel)
{
	core2_header * p = dev;
	p->current_stack = sel;
}

unsigned int vm_getStackPC(void *dev)
{
	core2_header * p = dev;
	return p->stack[p->current_stack];
}

void vm_setStackPC(void *dev, unsigned int pc)
{
	core2_header * p = dev;
	p->stack[p->current_stack] = pc;
}

/* *******************************************************************************
	Interruptions and stuff
 ******************************************************************************* */
 
void vm_raiseInt(void *dev)
{
	vm_opcode	intJump;
	vm_intbytes pos;
	
	intJump.parse3op11k.op = CORE_OP_CALL;
	
	/* set the call destination */
	pos.data = CORE_VECTOR_INT;
	intJump.parse3op11k.k0 = pos.bytes.low;
	intJump.parse3op11k.k1 = pos.bytes.high;

	vm_runOp(&intJump, dev);
}

/* *******************************************************************************
	Opcode dissection
 ******************************************************************************* */

void vm_runOp(vm_opcode *opcode, void *dev)
{
	char 		flagsum = 1;
	vm_opcode 	opBitAligned;

	opBitAligned.packed16 = opcode->packed16 << 1;


	#ifdef PERIPH_USART
		vm_services_usart(dev, opcode);
	#endif
	

	switch (opcode->parse3op11k.op)
	{
		case CORE_OP_GOTO: // goto <dir>
			vm_emu_goto(dev, opcode);
			flagsum = 0;
			break;
		case CORE_OP_CALL: // call <dir>
			vm_emu_call(dev, opcode);
			flagsum = 0;
			break;
	}
	
	switch (opBitAligned.parse4op7f.op)
	{
		case CORE_OP_BCF:
			vm_emu_bsf_bcf(dev, opcode, 0);
			break;		
		case CORE_OP_BSF:
			vm_emu_bsf_bcf(dev, opcode, 1);
			break;

		case CORE_OP_BTFSC:
			vm_emu_btfss_btfsc(dev, opcode, 0);
			break;
		case CORE_OP_BTFSS:
			vm_emu_btfss_btfsc(dev, opcode, 1);
			break;

		case CORE_OP_MOVLW:
			vm_setW(dev, opcode->opcode.data);
			break;

		case CORE_OP_RETLW:
			vm_setW(dev, opcode->opcode.data);
			vm_emu_return(dev);
			break;
	}	

	switch (opcode->parse5op8k.op)
	{
		case CORE_OP_ADDLW:
			vm_emu_addlw(dev, opcode);
			break;

		case CORE_OP_SUBLW:
			vm_emu_sublw(dev, opcode);
			break;
	}

	switch (opcode->parse6op8k.op)
	{
		case CORE_OP_ZERO:
			if (opcode->parse6op7f.d) // if eight bit is set, then its movwf op
				vm_emu_movwf(dev, opcode);
			else	
				switch (opcode->opcode.data & 0xF) // lower nibble contains the operation
				{
					case CORE_OP_RETFIE:
						vm_emu_retfie(dev);
						break;

					case CORE_OP_RETURN:
						vm_emu_return(dev);
						break;

					case CORE_OP_NOP:
						break;
				}
			
			break;

		case CORE_OP_ANDLW:
			vm_emu_andlw(dev, opcode);
			break;

		case CORE_OP_XORLW:
			vm_emu_iorlw_xorlw(dev, opcode, 0);
			break;

		case CORE_OP_IORLW:
			vm_emu_iorlw_xorlw(dev, opcode, 1);
			break;

		case CORE_OP_SWAPF:
			vm_emu_swapf(dev, opcode);
			break;

		case CORE_OP_DECFSZ:
			vm_emu_incfsz_decfsz(dev, opcode, 0);
			break;

		case CORE_OP_INCFSZ:
			vm_emu_incfsz_decfsz(dev, opcode, 1);
			break;
		
		case CORE_OP_XORWF:
			vm_emu_xorwf_iorwf(dev, opcode, 0); 
			break;

		case CORE_OP_IORWF:
			vm_emu_xorwf_iorwf(dev, opcode, 1); 
			break;

		case CORE_OP_SUBWF:
			vm_emu_subwf_addwf(dev, opcode, 0);
			break;

		case CORE_OP_ADDWF:
			vm_emu_subwf_addwf(dev, opcode, 1);
			break;

		case CORE_OP_ANDWF:
			vm_emu_andwf(dev, opcode);
			break;

		case CORE_OP_CLRW_CLRF:
			vm_emu_clrw_clrf(dev, opcode);
			break;

		case CORE_OP_DECF:
			vm_emu_incf_decf(dev, opcode, 0);
			break;

		case CORE_OP_INCF:
			vm_emu_incf_decf(dev, opcode, 1);
			break;

		case CORE_OP_COMF:
			vm_emu_comf(dev, opcode);
			break;

		case CORE_OP_MOVF:
			vm_emu_movf(dev, opcode);
			break;

		case CORE_OP_RLF:
			vm_emu_rlf_rrf(dev, opcode, 0);
			break;

		case CORE_OP_RRF:
			vm_emu_rlf_rrf(dev, opcode, 1);
			break;
	}	

	if (flagsum)
		vm_sumPC(dev);
}